<?php

/*
 * [HZTCcms] (C)2001-2018 AoSheWeb Inc.
 * This is NOT a freeware, use is subject to license terms.
 * 
 * Document   : RBAC.class.php
 * Encoding   : UTF-8
 * Created on : 2011-11-23, 9:45:23
 * Author     : leefire yunzhi.li.08@gmail.com 464328282
 * Site       : http://www.aosheweb.com
 * Description: 基于角色的数据库方式验证类
 */
/*
 * 
 * 对于当前角色验证的基本表结构


  DROP SCHEMA IF EXISTS `hztcdb` ;
  CREATE SCHEMA IF NOT EXISTS `hztcdb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
  USE `hztcdb` ;

  -- -----------------------------------------------------
  -- Table `hztcdb`.`hztc_access`
  -- -----------------------------------------------------
  DROP TABLE IF EXISTS `hztcdb`.`hztc_access` ;

  CREATE  TABLE IF NOT EXISTS `hztcdb`.`hztc_access` (
  `rid` SMALLINT(10) UNSIGNED NOT NULL COMMENT '角色id' ,
  `nid` SMALLINT(10) UNSIGNED NOT NULL COMMENT '节点id' ,
  `limit` SMALLINT(10) NOT NULL DEFAULT -1 COMMENT '限制某个操作的次数\n\n其中的层级关系是：分组/应用-》模块-》操作。\n\n总体的限制是：在每（period）（unit）里，限制操作（limit）次\n\n-1：不限；0：禁止' ,
  `period` SMALLINT(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '时间的长久计数\n0：不限' ,
  `unit` CHAR(10) NOT NULL DEFAULT '秒' COMMENT '单位：秒、分、时、天、周、月、季度、年' )
  ENGINE = MyISAM
  DEFAULT CHARACTER SET = utf8
  COLLATE = utf8_general_ci
  COMMENT = '角色与节点访问对应权限表' ;


  -- -----------------------------------------------------
  -- Table `hztcdb`.`hztc_admin`
  -- -----------------------------------------------------
  DROP TABLE IF EXISTS `hztcdb`.`hztc_admin` ;

  CREATE  TABLE IF NOT EXISTS `hztcdb`.`hztc_admin` (
  `id` SMALLINT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '管理员用户id' ,
  `rid` SMALLINT(10) UNSIGNED NOT NULL COMMENT '角色id，记录管理所处的权限角色组' ,
  `username` VARCHAR(45) NOT NULL COMMENT '系统管理员的用户名，用于系统登陆。' ,
  `password` VARCHAR(45) NOT NULL COMMENT '登陆密码' ,
  `realname` VARCHAR(45) NULL COMMENT '真实姓名' ,
  `email` VARCHAR(45) NOT NULL COMMENT '系统管理员电子邮箱，可以用于系统登陆。' ,
  `qq` VARCHAR(30) NULL ,
  `regtime` INT(11) UNSIGNED NOT NULL COMMENT '注册时间' ,
  `regip` VARCHAR(15) NOT NULL COMMENT '注册IP' ,
  `lastlogintime` INT(11) UNSIGNED NOT NULL COMMENT '最后登陆时间' ,
  `lastloginip` VARCHAR(15) NOT NULL COMMENT '最后登陆IP' ,
  `logincount` SMALLINT(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '登陆次数' ,
  `updatetime` INT(11) UNSIGNED NOT NULL COMMENT '最近一次更新时间，注册时，等于注册时间' ,
  `updateip` VARCHAR(15) NOT NULL COMMENT '更新IP' ,
  `issystem` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为系统内置，1：是；0：不是，系统内置管理员，不可以删除' ,
  `status` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态，0：禁用；1：启用；-1：删除。' ,
  `remark` VARCHAR(200) NULL COMMENT '备注说明' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `id_UNIQUE` (`id` ASC) ,
  UNIQUE INDEX `username_UNIQUE` (`username` ASC) ,
  UNIQUE INDEX `email_UNIQUE` (`email` ASC) )
  ENGINE = MyISAM
  DEFAULT CHARACTER SET = utf8
  COLLATE = utf8_general_ci,
  COMMENT = '华智天成系统管理员记录表' ;


  -- -----------------------------------------------------
  -- Table `hztcdb`.`hztc_role`
  -- -----------------------------------------------------
  DROP TABLE IF EXISTS `hztcdb`.`hztc_role` ;

  CREATE  TABLE IF NOT EXISTS `hztcdb`.`hztc_role` (
  `id` SMALLINT(10) UNSIGNED NOT NULL AUTO_INCREMENT ,
  `rolename` VARCHAR(45) NOT NULL COMMENT '角色名称' ,
  `roletitle` VARCHAR(45) NULL COMMENT '角色标题' ,
  `rolepid` SMALLINT(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '角色父ID' ,
  `rolepath` VARCHAR(100) NOT NULL COMMENT '角色层级关系路径' ,
  `issystem` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否为系统内置，1：是；0：不是，系统内置管理员，不可以删除' ,
  `status` TINYINT(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '角色状态：1.启用；0.禁用；' ,
  `remark` VARCHAR(200) NULL COMMENT '角色备注信息' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `id_UNIQUE` (`id` ASC) )
  ENGINE = MyISAM
  DEFAULT CHARACTER SET = utf8
  COLLATE = utf8_general_ci,
  COMMENT = '角色表' ;


  -- -----------------------------------------------------
  -- Table `hztcdb`.`hztc_node`
  -- -----------------------------------------------------
  DROP TABLE IF EXISTS `hztcdb`.`hztc_node` ;

  CREATE  TABLE IF NOT EXISTS `hztcdb`.`hztc_node` (
  `id` SMALLINT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '节点ID' ,
  `nodename` VARCHAR(45) NOT NULL COMMENT '节点名称' ,
  `nodetitle` VARCHAR(45) NULL COMMENT '节点标题' ,
  `nodepid` SMALLINT(10) UNSIGNED NOT NULL COMMENT '节点父级ID' ,
  `isauth` TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否需要验证，0：不需要；1需要。' ,
  `nodesort` SMALLINT(3) NOT NULL DEFAULT 0 COMMENT '节点排序，数字越小越排前' ,
  `nodelevel` SMALLINT(2) NOT NULL COMMENT '节点级别：1. 应用或分组；2.模块；3.方法' ,
  `nodepath` VARCHAR(100) NOT NULL COMMENT '节点的层级路径' ,
  `status` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '节点状态：0.禁用；1.启用；' ,
  `remark` VARCHAR(200) NULL COMMENT '备注信息' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `id_UNIQUE` (`id` ASC) )
  ENGINE = MyISAM
  DEFAULT CHARACTER SET = utf8
  COLLATE = utf8_general_ci,
  COMMENT = '系统节点表' ;

 */

class RBAC extends Think {
    /*
     * 用户登陆认证
     * @access  static public
     * @param   array   $map        认证时的认证条件
     * @param   strring $model      认证时数据所属模型
     * @return  array   $rs         认证结果
     */

    static public function authenticate($map, $model='') {
	if (empty($model)) {
	    $model = C('USER_AUTH_MODEL');
	}
	return M($model)->where($map)->find();
    }

    /*
     * 认证信息结果保存到session
     * @access  static public
     * @param   string  $authId
     * @return  void
     */

    static public function saveAccessList($authId = null) {
	if (null === $authId) {
	    $authId = $_SESSION[C(USER_AUTH_KEY)];
	}
	if (C('USER_AUTH_TYPE') != 2 && !$_SESSION[C('ADMIN_AUTH_KEY')]) {
	    $_SESSION['_ACCESS_LIST'] = RBAC::getAccessList($authId);
	}
    }

    /*
     * 获取认证后权限列表
     * @access  static public
     * @param   string  $authId     认证用户ID
     * @return  array   $access     权限列表
     */

    static public function getAccessList($authId) {
	$db = Db::getInstance();
	$table = array(
	    'role' => C('RBAC_ROLE_TABLE'),
	    'user' => C('RBAC_USER_TABLE'),
	    'access' => C('RBAC_ACCESS_TABLE'),
	    'node' => C('RBAC_NODE_TABLE')
	);
	// 查询拥有权限的应用或是分组
	$sql = "select node.id,node.nodename from " .
		$table['role'] . " as role," .
		$table['user'] . " as user," .
		$table['access'] . " as access ," .
		$table['node'] . " as node " .
		" where user.id='{$authId}' and user.rid=role.id " .
		" and access.rid=role.id and role.status=1 and access.nid=node.id " .
		" and node.nodelevel=1 and node.status=1";
	$apps = $db->query($sql);
	// 定义访问权限列表
	$access = array();
	// 再根据每一个不同的应用来定位拥有权限的模型
	foreach ($apps as $key => $app) {
	    $appId = $app['id'];
	    $appName = $app['nodename'];
	    $access[strtoupper($appName)] = array();
	    $sql = "select node.id,node.nodename from " .
		    $table['role'] . " as role," .
		    $table['user'] . " as user," .
		    $table['access'] . " as access ," .
		    $table['node'] . " as node " .
		    " where user.id='{$authId}' and user.rid=role.id " .
		    " and access.rid=role.id and role.status=1 and access.nid=node.id " .
		    " and node.nodelevel=2 and node.nodepid={$appId} and node.status=1";

	    $modules = $db->query($sql);
	    // 根据每一个模型来定位拥有权限的方法
	    foreach ($modules as $key => $module) {
		$moduleId = $module['id'];
		$moduleName = $module['nodename'];
		$sql = "select node.id,node.nodename,access.stint,access.period,access.unit from " .
			$table['role'] . " as role," .
			$table['user'] . " as user," .
			$table['access'] . " as access ," .
			$table['node'] . " as node " .
			" where user.id='{$authId}' and user.rid=role.id " .
			" and access.rid=role.id and role.status=1 and access.nid=node.id " .
			" and node.nodelevel=3 and node.nodepid={$moduleId} and node.status=1";

		$actionRs = $db->query($sql);
		foreach ($actionRs as $action) {
		    $actions[$action['nodename']] = array(
			'id' => $action['id'],
			'stint' => $action['stint'],
			'period' => $action['period'],
			'unit' => $action['unit']
		    );
		}
		$access[strtoupper($appName)][strtoupper($moduleName)] = array_change_key_case($actions, CASE_UPPER);
	    }
	}

	//print_r($access);
	return $access;
    }

    /*
     * 
     */

    static public function accessDecision($app = APP_NAME) {
	// 是否需要验证当前操作方法
	if (RBAC::checkAccess()) {
	    $accessGuid = md5($app . MODULE_NAME . ACTION_NAME);
	    if (empty($_SESSION[C('ADMIN_AUTH_KEY')])) {
		if (C('USER_AUTH_TYPE') == 2) {
		    //加强验证和即时验证模式 更加安全 后台权限修改可以即时生效
		    //通过数据库进行访问检查
		    $accessList = RBAC::getAccessList($_SESSION[C('USER_AUTH_KEY')]);
		} else {
		    // 如果是管理员或者当前操作已经认证过，无需再次认证
		    if ($_SESSION[$accessGuid]) {
			return true;
		    }
		    //登录验证模式，比较登录后保存的权限访问列表
		    $accessList = $_SESSION['_ACCESS_LIST'];
		}
		//判断是否为组件化模式，如果是，验证其全模块名
		$module = defined('P_MODULE_NAME') ? P_MODULE_NAME : MODULE_NAME;

		// 分操作的执行次数与频率来确定权限
		$stint = $accessList[strtoupper($app)][strtoupper($module)][strtoupper(ACTION_NAME)]['stint'];
		$period = $accessList[strtoupper($app)][strtoupper($module)][strtoupper(ACTION_NAME)]['period'];
		$unit = $accessList[strtoupper($app)][strtoupper($module)][strtoupper(ACTION_NAME)]['unit'];
		
		if ($stint == 0) {
		    $_SESSION[$accessGuid] = false;
		    return false;
		} elseif ($stint < 0) {
		    $_SESSION[$accessGuid] = true;
		} else {
		    if ($period == 0) {
			$_SESSION[$accessGuid] = true;
		    } else {
			// 与操作日志有关
		    }
		}
	    } else {
		// 超级管理员无需认证
		return true;
	    }
	}
	return true;
    }

    /*
     * 检查操作是否需要权限认证
     * @access  static public
     * @param   void
     * @return  boolean             判断最后的验证结果
     */

    static public function checkAccess() {
	// 判断是否开启了权限认证
	if (C('USER_AUTH_ON')) {
	    $db = Db::getInstance();
	    // 涉及到的相关表
	    $table = array(
		'role' => C('RBAC_ROLE_TABLE'),
		'user' => C('RBAC_USER_TABLE'),
		'access' => C('RBAC_ACCESS_TABLE'),
		'node' => C('RBAC_NODE_TABLE')
	    );

	    // sql检索语句
	    $sqlForModule = "select node.id, node.nodename from " .
		    $table['node'] . " as node" .
		    " where node.isauth = 1 and node.nodelevel = 2 and " .
		    "upper(node.nodename) = upper('" . MODULE_NAME . "')";

	    $modules = $db->query($sqlForModule);

	    // 判断当前模块是否要认证
	    if (!empty($modules)) {
		$sqlForAction = "select node.id, node.nodename from " .
			$table['node'] . " as node" .
			" where node.isauth = 1 and node.nodelevel = 3 and " .
			"node.nodepid = " . $modules[0]['id'] . " and node.nodelevel = 3" .
			" and upper(node.nodename) = upper('" . ACTION_NAME . "')";

		$actions = $db->query($sqlForAction);
		if (!empty($actions)) {
		    return true;
		} else {
		    return false;
		}
	    } else {
		return false;
	    }
	}
    }

}

?>
